/*
 * md5.cpp
 * A MD5 algorithem imply
 *
 * @auther Miles Dung
 * @date May 26 2011
 *
 * This file is part of md5 net based cracker project, which
 * released under the GNU GPL v2 or v3 (whichever you like)
 * This project is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY, without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU General Public License for more details.
 */
#include "md5.h"


inline UINT_32 md5::F(UINT_32 X,UINT_32 Y,UINT_32 Z)
{
    return (X&Y)|((~X)&Z);
}

inline UINT_32 md5::G(UINT_32 X,UINT_32 Y,UINT_32 Z)
{
    return (X&Z)|(Y&(~Z));
}

inline UINT_32 md5::H(UINT_32 X,UINT_32 Y,UINT_32 Z)
{
    return X^Y^Z;
}

inline UINT_32 md5::I(UINT_32 X,UINT_32 Y,UINT_32 Z)
{
    return Y^(X|(~Z));
}

inline void md5::FF(UINT_32& a, UINT_32 b, UINT_32 c, UINT_32 d,
                    UINT_32 Mj, UINT_32 s, UINT_32 ti)
{
    a=((a+F(b,c,d)+Mj+ti)<<s) | ((a+F(b,c,d)+Mj+ti)>>(32-s));
    a+=b;
}

inline void md5::GG(UINT_32& a, UINT_32 b, UINT_32 c, UINT_32 d,
                    UINT_32 Mj, UINT_32 s, UINT_32 ti)
{
    a=((a+G(b,c,d)+Mj+ti)<<s) | ((a+G(b,c,d)+Mj+ti)>>(32-s));
    a+=b;
}

inline void md5::HH(UINT_32& a, UINT_32 b, UINT_32 c, UINT_32 d,
                    UINT_32 Mj, UINT_32 s, UINT_32 ti)
{
    a=((a+H(b,c,d)+Mj+ti)<<s) | ((a+H(b,c,d)+Mj+ti)>>(32-s));
    a+=b;
}

inline void md5::II(UINT_32& a, UINT_32 b, UINT_32 c, UINT_32 d,
                    UINT_32 Mj, UINT_32 s, UINT_32 ti)
{
    a=((a+I(b,c,d)+Mj+ti)<<s) | ((a+I(b,c,d)+Mj+ti)>>(32-s));
    a+=b;
}

void md5::md5_init()
{
    a = A;
    b = B;
    c = C;
    d = D;
}

void md5::md5_calc()
{
    //really damn thing, played me for a long while
    //don't know why, just what RFC132 says
    UINT_32 AA=a;
    UINT_32 BB=b;
    UINT_32 CC=c;
    UINT_32 DD=d;
    //ROUND 1
    FF(a, b, c, d, digest.dig32[0], 7, 0xd76aa478);
    FF(d, a, b, c, digest.dig32[1], 12, 0xe8c7b756);
    FF(c, d, a, b, digest.dig32[2], 17, 0x242070db);
    FF(b, c, d, a, digest.dig32[3], 22, 0xc1bdceee);
    FF(a, b, c, d, digest.dig32[4], 7, 0xf57c0faf);
    FF(d, a, b, c, digest.dig32[5], 12, 0x4787c62a);
    FF(c, d, a, b, digest.dig32[6], 17, 0xa8304613);
    FF(b, c, d, a, digest.dig32[7], 22, 0xfd469501);
    FF(a, b, c, d, digest.dig32[8], 7, 0x698098d8);
    FF(d, a, b, c, digest.dig32[9], 12, 0x8b44f7af);
    FF(c, d, a, b, digest.dig32[10], 17, 0xffff5bb1);
    FF(b, c, d, a, digest.dig32[11], 22, 0x895cd7be);
    FF(a, b, c, d, digest.dig32[12], 7, 0x6b901122);
    FF(d, a, b, c, digest.dig32[13], 12, 0xfd987193);
    FF(c, d, a, b, digest.dig32[14], 17, 0xa679438e);
    FF(b, c, d, a, digest.dig32[15], 22, 0x49b40821);
    //ROUND 2
    GG(a, b, c, d, digest.dig32[1], 5, 0xf61e2562);
    GG(d, a, b, c, digest.dig32[6], 9, 0xc040b340);
    GG(c, d, a, b, digest.dig32[11], 14, 0x265e5a51);
    GG(b, c, d, a, digest.dig32[0], 20, 0xe9b6c7aa);
    GG(a, b, c, d, digest.dig32[5], 5, 0xd62f105d);
    GG(d, a, b, c, digest.dig32[10], 9, 0x02441453);
    GG(c, d, a, b, digest.dig32[15], 14, 0xd8a1e681);
    GG(b, c, d, a, digest.dig32[4], 20, 0xe7d3fbc8);
    GG(a, b, c, d, digest.dig32[9], 5, 0x21e1cde6);
    GG(d, a, b, c, digest.dig32[14], 9, 0xc33707d6);
    GG(c, d, a, b, digest.dig32[3], 14, 0xf4d50d87);
    GG(b, c, d, a, digest.dig32[8], 20, 0x455a14ed);
    GG(a, b, c, d, digest.dig32[13], 5, 0xa9e3e905);
    GG(d, a, b, c, digest.dig32[2], 9, 0xfcefa3f8);
    GG(c, d, a, b, digest.dig32[7], 14, 0x676f02d9);
    GG(b, c, d, a, digest.dig32[12], 20, 0x8d2a4c8a);
    //ROUND 3
    HH(a, b, c, d, digest.dig32[5], 4, 0xfffa3942);
    HH(d, a, b, c, digest.dig32[8], 11, 0x8771f681);
    HH(c, d, a, b, digest.dig32[11], 16, 0x6d9d6122);
    HH(b, c, d, a, digest.dig32[14], 23, 0xfde5380c);
    HH(a, b, c, d, digest.dig32[1], 4, 0xa4beea44);
    HH(d, a, b, c, digest.dig32[4], 11, 0x4bdecfa9);
    HH(c, d, a, b, digest.dig32[7], 16, 0xf6bb4b60);
    HH(b, c, d, a, digest.dig32[10], 23, 0xbebfbc70);
    HH(a, b, c, d, digest.dig32[13], 4, 0x289b7ec6);
    HH(d, a, b, c, digest.dig32[0], 11, 0xeaa127fa);
    HH(c, d, a, b, digest.dig32[3], 16, 0xd4ef3085);
    HH(b, c, d, a, digest.dig32[6], 23, 0x04881d05);
    HH(a, b, c, d, digest.dig32[9], 4, 0xd9d4d039);
    HH(d, a, b, c, digest.dig32[12], 11, 0xe6db99e5);
    HH(c, d, a, b, digest.dig32[15], 16, 0x1fa27cf8);
    HH(b, c, d, a, digest.dig32[2], 23, 0xc4ac5665);
    //ROUND 4
    II(a, b, c, d, digest.dig32[0], 6, 0xf4292244);
    II(d, a, b, c, digest.dig32[7], 10, 0x432aff97);
    II(c, d, a, b, digest.dig32[14], 15, 0xab9423a7);
    II(b, c, d, a, digest.dig32[5], 21, 0xfc93a039);
    II(a, b, c, d, digest.dig32[12], 6, 0x655b59c3);
    II(d, a, b, c, digest.dig32[3], 10, 0x8f0ccc92);
    II(c, d, a, b, digest.dig32[10], 15, 0xffeff47d);
    II(b, c, d, a, digest.dig32[1], 21, 0x85845dd1);
    II(a, b, c, d, digest.dig32[8], 6, 0x6fa87e4f);
    II(d, a, b, c, digest.dig32[15], 10, 0xfe2ce6e0);
    II(c, d, a, b, digest.dig32[6], 15, 0xa3014314);
    II(b, c, d, a, digest.dig32[13], 21, 0x4e0811a1);
    II(a, b, c, d, digest.dig32[4], 6, 0xf7537e82);
    II(d, a, b, c, digest.dig32[11], 10, 0xbd3af235);
    II(c, d, a, b, digest.dig32[2], 15, 0x2ad7d2bb);
    II(b, c, d, a, digest.dig32[9], 21, 0xeb86d391);

    a += AA;
    b += BB;
    c += CC;
    d += DD;
}
md5::md5(string& str)
{
    md5_init();

    UINT_64 len = str.length();
    for (int i=0; i<len; i++)
    {
        digest.dig8[i] = str[i];
    }

    //append 1, according to rfc1321
    digest.dig8[len]=(UINT_8)0x80;
    for (int i=len+1; i<56; ++i)
    {
        digest.dig8[i]=0x0;
    }
    //muilt 8 really annoying, forgot several times!
    digest.dig64[7]=len*8;

    md5_calc();
}

md5::md5(ifstream& fin)
{
    UINT_64 file_size;
    fin.seekg(0, ios::end);
    file_size=fin.tellg();

    fin.seekg(0, ios::beg);
    UINT_64 counter=0;
    md5_init();

    //we use 1M buffer hoping the caculation
    //terminates faster
    UINT_8 buffer[1024000];
    UINT_32 buf_counter = 0;
    while(file_size-counter>=64)
    {
        if (buf_counter == 0)
        {
            fin.read((char*)buffer, sizeof(buffer));
        }
        for (int i=0; i<64; i++, buf_counter++)
        {
            digest.dig8[i] = buffer[buf_counter];
        }
        md5_calc();
        if (buf_counter == 1024000)
        {
            buf_counter = 0;
        }
        counter+=64;
    }
    UINT_8 byte_left=file_size-counter;

    memset(digest.dig8, 0, sizeof(digest));
    if (buf_counter == 0)
    {
        fin.read((char*)buffer, sizeof(buffer));
    }

    for (int i=0; i<byte_left; i++, buf_counter++)
    {
        digest.dig8[i] = buffer[buf_counter];
    }

    //append 1 first
//    digest.dig8[bit_left/8] |= (UINT_8)0x80 >> (bit_left%8);
    digest.dig8[byte_left] = 0x80;

    if(++byte_left<=56)
    {
        //append the length
        digest.dig64[7] = file_size*8;
        md5_calc();
    }
    else
    {
        md5_calc();
        memset(digest.dig8, 0, sizeof(digest));
        digest.dig64[7] = file_size*8;
        md5_calc();
    }

}

string md5::md5_get_hash()
{
    result.res32[0]=a;
    result.res32[1]=b;
    result.res32[2]=c;
    result.res32[3]=d;

    std::ostringstream hash;
    char tmp[33];
    for (int i=0; i<16; i++)
    {
        sprintf(tmp+2*i, "%02x", result.res8[i]);
    }
    tmp[32]='\0';
    hash << tmp;
//    std::cout <<hash;
    return hash.str();
}
